/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.direct;

import com.android.dx.cf.iface.AttributeList;
import com.android.dx.cf.iface.Member;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.cf.iface.ParseObserver;
import com.android.dx.cf.iface.StdAttributeList;
import com.android.dx.rop.cst.ConstantPool;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;

/**
 * Parser for lists of class file members (that is, fields and methods).
 */
abstract/* package */class MemberListParser {

	/** {@code non-null;} the class file to parse from */
	private final DirectClassFile cf;

	/** {@code non-null;} class being defined */
	private final CstType definer;

	/** offset in the byte array of the classfile to the start of the list */
	private final int offset;

	/** {@code non-null;} attribute factory to use */
	private final AttributeFactory attributeFactory;

	/**
	 * {@code >= -1;} the end offset of this list in the byte array of the
	 * classfile, or {@code -1} if not yet parsed
	 */
	private int endOffset;

	/** {@code null-ok;} parse observer, if any */
	private ParseObserver observer;

	/**
	 * Constructs an instance.
	 * 
	 * @param cf
	 *            {@code non-null;} the class file to parse from
	 * @param definer
	 *            {@code non-null;} class being defined
	 * @param offset
	 *            offset in {@code bytes} to the start of the list
	 * @param attributeFactory
	 *            {@code non-null;} attribute factory to use
	 */
	public MemberListParser(DirectClassFile cf, CstType definer, int offset,
			AttributeFactory attributeFactory) {
		if (cf == null) {
			throw new NullPointerException("cf == null");
		}

		if (offset < 0) {
			throw new IllegalArgumentException("offset < 0");
		}

		if (attributeFactory == null) {
			throw new NullPointerException("attributeFactory == null");
		}

		this.cf = cf;
		this.definer = definer;
		this.offset = offset;
		this.attributeFactory = attributeFactory;
		this.endOffset = -1;
	}

	/**
	 * Gets the end offset of this constant pool in the {@code byte[]} which it
	 * came from.
	 * 
	 * @return {@code >= 0;} the end offset
	 */
	public int getEndOffset() {
		parseIfNecessary();
		return endOffset;
	}

	/**
	 * Sets the parse observer for this instance.
	 * 
	 * @param observer
	 *            {@code null-ok;} the observer
	 */
	public final void setObserver(ParseObserver observer) {
		this.observer = observer;
	}

	/**
	 * Runs {@link #parse} if it has not yet been run successfully.
	 */
	protected final void parseIfNecessary() {
		if (endOffset < 0) {
			parse();
		}
	}

	/**
	 * Gets the count of elements in the list.
	 * 
	 * @return the count
	 */
	protected final int getCount() {
		ByteArray bytes = cf.getBytes();
		return bytes.getUnsignedShort(offset);
	}

	/**
	 * Gets the class file being defined.
	 * 
	 * @return {@code non-null;} the class
	 */
	protected final CstType getDefiner() {
		return definer;
	}

	/**
	 * Gets the human-oriented name for what this instance is parsing.
	 * Subclasses must override this method.
	 * 
	 * @return {@code non-null;} the human oriented name
	 */
	protected abstract String humanName();

	/**
	 * Gets the human-oriented string for the given access flags. Subclasses
	 * must override this method.
	 * 
	 * @param accessFlags
	 *            the flags
	 * @return {@code non-null;} the string form
	 */
	protected abstract String humanAccessFlags(int accessFlags);

	/**
	 * Gets the {@code CTX_*} constant to use when parsing attributes.
	 * Subclasses must override this method.
	 * 
	 * @return {@code non-null;} the human oriented name
	 */
	protected abstract int getAttributeContext();

	/**
	 * Sets an element in the list. Subclasses must override this method.
	 * 
	 * @param n
	 *            which element
	 * @param accessFlags
	 *            the {@code access_flags}
	 * @param nat
	 *            the interpreted name and type (based on the two
	 *            {@code *_index} fields)
	 * @param attributes
	 *            list of parsed attributes
	 * @return {@code non-null;} the constructed member
	 */
	protected abstract Member set(int n, int accessFlags, CstNat nat,
			AttributeList attributes);

	/**
	 * Does the actual parsing.
	 */
	private void parse() {
		int attributeContext = getAttributeContext();
		int count = getCount();
		int at = offset + 2; // Skip the count.

		ByteArray bytes = cf.getBytes();
		ConstantPool pool = cf.getConstantPool();

		if (observer != null) {
			observer.parsed(bytes, offset, 2,
					humanName() + "s_count: " + Hex.u2(count));
		}

		for (int i = 0; i < count; i++) {
			try {
				int accessFlags = bytes.getUnsignedShort(at);
				int nameIdx = bytes.getUnsignedShort(at + 2);
				int descIdx = bytes.getUnsignedShort(at + 4);
				CstString name = (CstString) pool.get(nameIdx);
				CstString desc = (CstString) pool.get(descIdx);

				if (observer != null) {
					observer.startParsingMember(bytes, at, name.getString(),
							desc.getString());
					observer.parsed(bytes, at, 0, "\n" + humanName() + "s[" + i
							+ "]:\n");
					observer.changeIndent(1);
					observer.parsed(bytes, at, 2, "access_flags: "
							+ humanAccessFlags(accessFlags));
					observer.parsed(bytes, at + 2, 2, "name: " + name.toHuman());
					observer.parsed(bytes, at + 4, 2,
							"descriptor: " + desc.toHuman());
				}

				at += 6;
				AttributeListParser parser = new AttributeListParser(cf,
						attributeContext, at, attributeFactory);
				parser.setObserver(observer);
				at = parser.getEndOffset();
				StdAttributeList attributes = parser.getList();
				attributes.setImmutable();
				CstNat nat = new CstNat(name, desc);
				Member member = set(i, accessFlags, nat, attributes);

				if (observer != null) {
					observer.changeIndent(-1);
					observer.parsed(bytes, at, 0, "end " + humanName() + "s["
							+ i + "]\n");
					observer.endParsingMember(bytes, at, name.getString(),
							desc.getString(), member);
				}
			} catch (ParseException ex) {
				ex.addContext("...while parsing " + humanName() + "s[" + i
						+ "]");
				throw ex;
			} catch (RuntimeException ex) {
				ParseException pe = new ParseException(ex);
				pe.addContext("...while parsing " + humanName() + "s[" + i
						+ "]");
				throw pe;
			}
		}

		endOffset = at;
	}
}
